# coding=utf-8
"""
author = jamon
"""

import asyncio
from typing import List, Optional
from obespoir.base.common_define import ConnectionStatus, RpcMsgType
from obespoir.base.global_object import GlobalObject
from obespoir.config import NodeType
from obespoir.rpcserver.connection_manager import RpcConnectionManager
from obespoir.rpcserver.session_cache import SessionCache
from obespoir.share.espoir_log import global_log


async def call_remote_service(node_name, command_id, message,
                              session_id=None, to=None,
                              msg_type=RpcMsgType.REQUEST):
    """

    :param msg_type:
    :param session_id:
    :param node_name: string, 节点名
    :param command_id: int
    :param message: string, 待发送的消息
    :param to: string, 消息最终发往哪里，为空时则根据消息ID发送
    :return: None
    """
    return await RpcConnectionManager().send_message(
        node_name, command_id, message,
        session_id=session_id, to=to, msg_type=msg_type)


def find_available_node(next_node_type, to=None):
    """
    寻找一个可用的节点，如果没有，则异步等待直到找到为止
    :param next_node_type:
    :param to: string, node name
    :return: connect node object id
    """
    global_log.debug("start", next_node_type=next_node_type, to=to)
    while True:
        if to and to in RpcConnectionManager().conns.keys() \
                and ConnectionStatus.ESTABLISHED == \
                RpcConnectionManager().conns[to]["status"]:
            # 如果明确传输的目标，且和目标节点有直接可用的rpc连接，则直接通过该连接发送消息；
            next_node = to
        else:
            avail_route = RpcConnectionManager().get_available_connection(
                NodeType.ROUTE)
            if not next_node_type or NodeType.ROUTE == next_node_type:
                # 选取一个可用的route节点
                next_node = avail_route
            else:
                next_node = RpcConnectionManager().get_available_connection(
                    next_node_type)
                if not next_node:
                    # 如果没有可用的next_node_type类型对应的节点, 则转发路由节点
                    next_node = avail_route

            if not next_node:
                global_log.error("all route node dead!!!",
                                 next_node_type=next_node_type, to=to)
                raise Exception()

        return next_node


async def push_message(next_node_type: Optional[NodeType], command_id: int, message: any,
                       session_id: str = None, to=None,
                       msg_type=RpcMsgType.REQUEST):
    """
    向其他节点推送消息
    :param msg_type: int, RpcMsgType
    :param next_node_type: int, 接下来发往的节点类型
    :param command_id: int
    :param message: string, 消息内容
    :param session_id: string, 会话ID（）
    :param to: string, 消息最终发往哪里，为空时则根据消息ID发送
    :return: None
    """
    if next_node_type == GlobalObject().type:
        # 参数错误，自己给自己发消息
        global_log.error("send to self", next_node_type=next_node_type,
                         command_id=command_id,
                         message=message, session_id=session_id, to=to,
                         msg_type=msg_type)
        raise Exception()

    next_node = SessionCache().get_node(session_id, next_node_type)
    if not next_node:
        next_node = find_available_node(next_node_type, to=to)
    SessionCache().add_cache(session_id, node_type=next_node_type,
                             node_id=next_node)
    global_log.debug("start", next_node=next_node, command_id=command_id,
                     message=message, session_id=session_id, to=to,
                     msg_type=msg_type)
    return await call_remote_service(
        next_node, command_id, message, session_id=session_id,
        to=to, msg_type=msg_type)


async def multicast_msg(session_ids: List[str], message_id: int, msg: str,
                        msg_type=RpcMsgType.NOTIFY):
    """
    多播
    """
    data = {
        "code": 200,
        "data": msg
    }
    for session in session_ids:
        to, _ = session.rsplit("_", 1)

        await push_message(NodeType.PROXY, message_id, data, session, to,
                           msg_type=msg_type)
